﻿using DirectShowLib;
using DirectShowLib.BDA;
using DirectShowLib.DES;
using DirectShowLib.DMO;
using DirectShowLib.MultimediaStreaming;
using DirectShowLib.SBE;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Text;
using System.Threading;

namespace SetTopBox.BDA
{
    class GraphUtil
    {
        private Device[] devs;
        private static int baseFreq = 474000;
        private static int increment = 8000;
        private static int[] uhfFreqs;

        private IFilterGraph2 fGraph;
        private IBaseFilter bdaNetworkProvider;
        public DsROTEntry rot;
        private static Guid BASEFILTER_GUID;
        private IDVBTuningSpace2 tSpace;
        private object bdaSource;
        private IBaseFilter bdaST;

        static GraphUtil()
        {
            BASEFILTER_GUID = typeof(IBaseFilter).GUID;
            uhfFreqs = new int[48];
            for (int i = 0; i < uhfFreqs.Length; i++)
            {
                uhfFreqs[i] = baseFreq + (i * increment);
            }
        }

        // Sets up a filter graph and finds good tuner/receiver combinations, then destroys the filtergraph.
        // Probably possible to create just one FG on this class and reuse.  Currently this FG gets destroyed,
        // future methods recreate it.
        public Device[] findDevices()
        {
            // 1)  Create graph
            IFilterGraph2 fGraph = (IFilterGraph2)new FilterGraph();
            //rot = new DsROTEntry(fGraph);
            // 2)  Add DVB-T network provider
            IBaseFilter bdaNetworkProvider = FilterGraphTools.AddFilterFromClsid(fGraph, typeof(DVBTNetworkProvider).GUID, "DVBT Network Provider");
            fGraph.AddFilter(bdaNetworkProvider, "DVBT Network Provider");
           
            // Enumerate all tuners
            ICreateDevEnum sourceDevEnum = (ICreateDevEnum)new CreateDevEnum();
            IEnumMoniker enumMoniker;
            int catResult = sourceDevEnum.CreateClassEnumerator(FilterCategory.BDASourceFiltersCategory, out enumMoniker, CDef.None);
            if (catResult == 0)
            {
                IMoniker[] bdaTunerMoniker = new IMoniker[1];
                List<Device> devices = null;
                object bdaTuner;
                while (enumMoniker.Next(1, bdaTunerMoniker, IntPtr.Zero) == 0)
                {
                    bdaTunerMoniker[0].BindToObject(null, null, ref BASEFILTER_GUID, out bdaTuner);
                    fGraph.AddFilter((IBaseFilter)bdaTuner, "BDA Tuner");
                    bool success = FilterGraphTools.ConnectFilters(fGraph, bdaNetworkProvider, (IBaseFilter)bdaTuner);
                    if (success)
                    {
                        Console.WriteLine("Connected OK!");
                    }
                    else
                    {
                        Console.WriteLine("Failed to connect");
                    }

                    ICreateDevEnum recDevEnum = (ICreateDevEnum)new CreateDevEnum();
                    IEnumMoniker enumRecDev;
                    recDevEnum.CreateClassEnumerator(FilterCategory.BDAReceiverComponentsCategory, out enumRecDev, CDef.None);
                    IMoniker[] recDevMoniker = new IMoniker[1];
                    object bdaReceiver;
                    bool succ2 = false;
                    while (enumRecDev.Next(1, recDevMoniker, IntPtr.Zero) == 0 && !succ2)
                    {
                        recDevMoniker[0].BindToObject(null, null, ref BASEFILTER_GUID, out bdaReceiver);
                        fGraph.AddFilter((IBaseFilter)bdaReceiver, "BDA Receiver");
                        succ2 = FilterGraphTools.ConnectFilters(fGraph, (IBaseFilter)bdaTuner, (IBaseFilter)bdaReceiver);
                        if (succ2)
                        {
                            Console.WriteLine("Yo!");
                            if (devices == null)
                            {
                                devices = new List<Device>();
                            }
                            devices.Add(new Device(DVBSystemType.Terrestrial, bdaTunerMoniker[0], recDevMoniker[0]));
                        }
                        else
                        {
                            Console.WriteLine("No!");
                        }
                        FilterGraphTools.Disconnect(fGraph, (IBaseFilter)bdaReceiver);
                        Marshal.ReleaseComObject(bdaReceiver);
                    }
                    FilterGraphTools.Disconnect(fGraph, (IBaseFilter)bdaTuner);
                    Marshal.ReleaseComObject(bdaTuner);
                }
                FilterGraphTools.Disconnect(fGraph, bdaNetworkProvider);
                Marshal.ReleaseComObject(bdaNetworkProvider);

                devs = new Device[devices.Count];
                devices.CopyTo(devs);
                return devs;
            }

            return null;
        }

        public void destroy()
        {
            
        }

        public void setupGraph(Device device)
        {
            // Device filters
            object bdaTuner = null;
            IBaseFilter bdaTIF = null;
            MPEG2Demultiplexer mp2demux = null;

            // Create graph
            fGraph = (IFilterGraph2)new FilterGraph();
            rot = new DsROTEntry(fGraph);

            // Add DVB-T network provider
            bdaNetworkProvider = FilterGraphTools.AddFilterFromClsid(fGraph, typeof(DVBTNetworkProvider).GUID, "DVBT Network Provider");
            fGraph.AddFilter(bdaNetworkProvider, "DVBT Network Provider");

            // Create tuning space
            tSpace = (IDVBTuningSpace2)new DVBTuningSpace();
            tSpace.put_SystemType(device.Network);
            tSpace.put_NetworkType("{216C62DF-6D7F-4E9A-8571-05F14EDB766A}");
            
            // Add tuningspace to device;
            (bdaNetworkProvider as ITuner).put_TuningSpace(tSpace);

            // Add device filters to the graph
            device.Source.BindToObject(null, null, ref BASEFILTER_GUID, out bdaSource);
            if (fGraph.AddFilter((IBaseFilter)bdaSource, "BDA Tuner") == 0)
            {
                Console.WriteLine("added BDA Source filter OK");

                device.Receiver.BindToObject(null, null, ref BASEFILTER_GUID, out bdaTuner);
                if (fGraph.AddFilter((IBaseFilter)bdaTuner, "BDA Receiver") == 0)
                {
                    Console.WriteLine("added BDA Receiver filter OK");

                    // Add MPEG2 Demux
                    mp2demux = new MPEG2Demultiplexer();
                    if (fGraph.AddFilter((IBaseFilter)mp2demux, "mpeg2 demux") == 0)
                    {
                        Console.WriteLine("added mpeg2 demux filter OK");

                        // Find sections and tables filter
                        DsDevice[] devices = DsDevice.GetDevicesOfCat(FilterCategory.BDATransportInformationRenderersCategory);
                        bool st = false, tif = false;
                        for (int i = 0; i < devices.Length; i++)
                        {
                            Console.WriteLine(devices[i].Name);
                            if (devices[i].Name.Equals("MPEG-2 Sections and Tables"))
                            {
                                // add filter
                                if (fGraph.AddSourceFilterForMoniker(devices[i].Mon, null, devices[i].Name, out bdaST) == 0)
                                {
                                    Console.WriteLine("added S&T filter OK");
                                    st = true;
                                }
                            } 
                            else if (devices[i].Name.Equals("BDA MPEG2 Transport Information Filter"))
                            {
                                // add filter
                                if (fGraph.AddSourceFilterForMoniker(devices[i].Mon, null, devices[i].Name, out bdaTIF) == 0)
                                {
                                    Console.WriteLine("added TIF filter OK");
                                    tif = true;
                                }
                            }
                        }

                        if (st && tif)
                        {
                            // Now connect them all up
                            if (FilterGraphTools.ConnectFilters(fGraph, bdaNetworkProvider, (IBaseFilter)bdaSource))
                            {
                                Console.WriteLine("Connected net provider to source");
                                if (FilterGraphTools.ConnectFilters(fGraph, (IBaseFilter)bdaSource, (IBaseFilter)bdaTuner))
                                {
                                    Console.WriteLine("Connected source to tuner");
                                    if (FilterGraphTools.ConnectFilters(fGraph, (IBaseFilter)bdaTuner, (IBaseFilter)mp2demux))
                                    {
                                        Console.WriteLine("Connected tuner to mp2 demux");

                                        if (FilterGraphTools.ConnectFilters(fGraph, (IBaseFilter)mp2demux, (IBaseFilter)bdaTIF))
                                        {
                                            Console.WriteLine("Connected demux to tif filter");

                                            if (FilterGraphTools.ConnectFilters(fGraph, (IBaseFilter)mp2demux, (IBaseFilter)bdaST))
                                            {
                                                Console.WriteLine("Connected demux to s&t filter");

                                                // let's go!
                                                (fGraph as IMediaControl).Run();
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        public void scan()
        { 
            // tuning info
            bool locked, present;
            int strength, quality;

            for (int j = 0; j < uhfFreqs.Length; j++)
            {
                Console.WriteLine("Scanning " + uhfFreqs[j]);
                LockChannel(tSpace, bdaSource, uhfFreqs[j], 8, out locked, out present, out strength, out quality);
                if (locked && quality > 50)
                {

                }
            }
        }

        private void renderGraph(IFilterGraph2 graph, IBaseFilter filter)
        {
            IEnumPins destPins;
            IPin[] destPin = new IPin[1];
            filter.EnumPins(out destPins);
            while (destPins.Next(destPin.Length, destPin, IntPtr.Zero) == 0)
            {
                PinDirection pinDir;
                destPin[0].QueryDirection(out pinDir);

                if (pinDir == PinDirection.Output)
                {
                    fGraph.Render(destPin[0]);
                }
            }
        }

        private void LockChannel(IDVBTuningSpace2 tSpace, object bdaSource, int freq, int bw, out bool locked, out bool present, out int strength, out int quality)
        {
            IDVBTuneRequest tReq;

            locked = false;
            present = false;
            quality = 0;
            strength = 0;

            FilterGraphTools.CreateDVBTTuneRequest(tSpace, out tReq, freq, bw);

            (bdaNetworkProvider as ITuner).put_TuneRequest(tReq);

            IBDA_Topology topology = (IBDA_Topology)bdaSource;

            int numNodeTypes;
            int maxNodeTypes = 32;
            int[] nodeTypes = new int[maxNodeTypes];
            topology.GetNodeTypes(out numNodeTypes, maxNodeTypes, nodeTypes);
            for (int l = 0; l < 10; l++)
            {
                for (int j = 0; j < numNodeTypes; j++)
                {
                    object controlNode;
                    if (topology.GetControlNode(0, 1, nodeTypes[j], out controlNode) == 0)
                    {
                        IBDA_SignalStatistics stats = (IBDA_SignalStatistics)controlNode;

                        int sampleTime;
                        stats.get_SampleTime(out sampleTime);

                        stats.get_SignalLocked(out locked);

                        stats.get_SignalPresent(out present);

                        stats.get_SignalQuality(out quality);

                        stats.get_SignalStrength(out strength);
                    }
                }

                if (quality > 0)
                {
                    break;
                }
                else
                {
                    Thread.Sleep(100);
                }
            }
            

            if (locked || (quality > 0))
            {
                Console.WriteLine("Signal locked = " + locked);
                Console.WriteLine("Signal present = " + present);
                Console.WriteLine("Signal quality = " + quality);
                Console.WriteLine("Signal strength = " + strength);

                return;
            }
        }
    }
}
